package tester

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/AlexWoo/arpc"
	"gitee.com/AlexWoo/arpc/alog"
	"gitee.com/AlexWoo/arpc/anet"
	"gitee.com/AlexWoo/arpc/ws"
)

var (
	slctx = &alog.LogCtx{
		Prefix: func() string { return "Server" },
	}
)

type Server struct {
	bn *anet.Net

	sc *script
	ts map[string]*tester
	tr *TestRecorder

	totoal uint32
	finish uint32
}

func CreateTesterServer(wg *sync.WaitGroup, addr string, path string, script string) *Server {
	s := &Server{
		ts: map[string]*tester{},
		tr: &TestRecorder{},
	}

	// init conf
	conf := &anet.Conf{}
	arpc.FillDefault(conf)
	conf.Path = path

	wsconf := &ws.ServerConf{}
	arpc.FillDefault(wsconf)
	wsconf.Addr = addr

	// init anet
	s.bn = anet.CreateNet(conf, ws.Connect, s, wg)

	// init script
	sc, err := parseFile(script, slctx)
	if err != nil {
		alog.LogError(slctx, "init script error: %v", err)
		return nil
	}
	s.sc = sc

	// init transport layer
	wss := ws.NewServer(wsconf)
	wss.RegisteHandler(conf.Path, func(c *ws.Connection) anet.Handler { return s.bn })

	wss.Run(wg)

	go func() {
		for {
			time.Sleep(10 * time.Second)
			s, f, finish, totoal := s.Report()
			fmt.Printf("... %s UAS Tester Result --- successd: %d failed: %d total: %d hangup: %d\n",
				time.Now().Format(timeFormat), s, f, totoal, totoal-finish)
		}
	}()

	return s
}

func (s *Server) Report() (uint32, uint32, uint32, uint32) {
	return s.tr.success, s.tr.failed, s.finish, s.totoal
}

func (s *Server) Dispatch(msg *anet.Msg) {
	name := msg.AppId + "/" + msg.ChannelId + "/" + msg.UserId

	te := s.ts[name]
	if te == nil {
		if msg.Type == anet.MsgType_Join && msg.Code == 0 {
			te = newTester(s, s.tr, s.sc)
			s.ts[name] = te
			atomic.AddUint32(&s.totoal, 1)
		} else {
			alog.LogError(slctx, "Receive [%s] but script not exist", msg.Str())
			return
		}
	}

	alog.LogInfo(te.lctx, "Receive msg [%s]", msg.Str())

	if msg.Type == anet.MsgType_Closed {
		delete(s.ts, name)
		atomic.AddUint32(&s.finish, 1)
		return
	}

	te.recvMsg(msg)
}

func (s *Server) Send(msg *anet.Msg, te *tester) {
	alog.LogInfo(te.lctx, "Send msg [%s]", msg.Str())
	s.bn.Send(msg, "")
}
